﻿#define _CRT_SECURE_NO_WARNINGS 1

//字符指针变量
//#include <stdio.h>
//int main()
//{
//	//字符指针变量的第一种用法
//	char a = 'w';
//	char* pa = &a;
//	printf("%c\n", a);
//	printf("%c\n", *pa);
//	*pa = 'a';
//	printf("%c\n", *pa);
//	//字符指针变量的第二种用法。
//	char const* pb = "hello,world!";	//这里的意思是将这一串字符串的首元素地址放在pb字符指针变量当中，然后打印时遇到了\0就结束了。
//	printf("%s\n", pb);
//
//	return 0;
//}


//看一道经典的类面试题目
//#include <stdio.h>
//int main()
//{
//	char str1[] = "welcome to bit.";
//	char str2[] = "welcome to bit.";
//
//	char const* str3 = "welcome to bit.";
//	char const* str4 = "welcome to bit.";
//
//	if (str1 == str2)
//	{
//		printf("str1 and str2 are same.\n");	
//	}
//	else
//	{
//		printf("str1 and str2 are not same.\n");//在这里我们看到输出已经知道：str1和str2是不相等的，这是因为它们分配在内存的空间地址是不同的
//	}
//	if (str3 == str4)
//	{
//		printf("str3 and str4 are same.\n");	//这里的输出我们知道：str3和str4是相等的，都为常量字符串，并且因为字符指针变量存放的相同的字符串时，其字符串的地址都是一致的。
//	}
//	else
//	{
//		printf("str3 and str4 are not same.\n");
//	}
//	return 0;
//}

//数组指针变量:顾名思义是一种存放数组地址的指针变量。
//其数组指针变量的类型为：int (*)[n] ，其中*要括起来是因为优先级小于[] ,*也只是指向指针变量的一个修饰而已。
// 在输出每个数组的元素时，我们可以认为 *p == arr，但是还要知道它们之间只是值相同而类型不相同，例如p的类型是int*，而arr的类型是int[n]。
//#include <stdio.h>
//int main()
//{
//	//数组指针变量的初始化
//	int arr[10] = { 0 };
//	//这里我们需要注意：pa是指针变量，int是指针变量指向的数组的元素类型，[n]这里的n是指数组的大小，同时还需要知道这里数组大小是不可以省略掉的
//	int (*pa)[10] = &arr;
//	//通过输出，我们发现&arr 和 pa  的输出是一样的，所以说明数组指针变量是存放数组地址的指针变量。
//	printf("%p\n", &arr);
//	printf("%p\n", pa);
//
//	//同时可以联想到学到的二级指针
//	int* brr[6] = { 0 };
//	int* (*pb)[6] = &brr;
//	//这里我们就需要知道，其数组的元素类型是int*
//	printf("%p\n", &brr);
//	printf("%p\n", pb);
//	return 0;
//}


//二维数组传参的本质

//以下是一个经典的二维数组传参程序	
//#include <stdio.h>
////这里我们知道：二维数组的第一行其实可以看成一维数组，而对于数组名来说就是数组首元素的地址，在这里也就等于了一维数组的地址
////所以我们总结得出：二维数组传参的本质：传递的是第一行的一维数组的地址，本质就是传地址。
//void paa(int (*pa)[5] /* == int arr[3][5]*/, int x, int y)	//所以这的数组形式可以改写为：int (*pa)[5] ,这里的pa就是第一行一维数组的地址。
//{
//	int i = 0;
//	int j = 0;
//	for (i = 0; i < x; i++)
//	{
//		for (j = 0; j < y; j++)
//		{
//			printf("%d ", *(*(pa+i)+j));	//注：这里的*(pa+i)，是由于pa指第一行一维数组的地址，加i是之后循环遍历到第二行乃至第i行的一维数组，之后解引用就是第一行一维数组的首元素
//			//也就通俗表达：arr[i][j] ==  *(*(pa+i)+j);*(pa+i) == arr[i] ,然后因为*(pa+i)的类型是int*，也是一个int*类型的指针，所以+j也就是在这个指针里的数前进j步。
//		}
//		printf("\n");
//	}
//}
//
//int main()
//{
//	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
//	paa(arr, 3, 5);
//	return 0;
//}


////函数指针变量
////所以我们通过这个程序可以总结，函数指针变量的形式为：数据类型 (*指针变量名)(对应类型/参数的个数，对应类型/参数的个数）
//void test()
//{
//	printf("haha\n");
//}
//int Add(int x, int y)
//{
//	return x + y;
//}
//
//int main()
//{
//	//尝试打印函数的地址
//	//通过输出，我们发现函数也是有地址的。
//	printf("test:  %p\n", test);
//	printf("&test: %p\n", &test);
//
//	//用函数指针变量存放函数，并进行打印地址
//	void (*pa)() = test;
//	void (*pb)() = &test;
//	//通过输出，我们发现与上面的程序输出是一致的。
//	printf("test:  %p\n", pa);
//	printf("&test: %p\n", pb);
//	//我们再次尝试一个函数
//	Add(3, 4);
//	int (*pc)(int x, int y) = Add;
//	//从输出结果来看，打印的地址是一致的。
//	printf("Add  %p\n", Add);
//	printf("&Add  %p\n", &Add);
//	printf("pc  %p\n", pc);
//	return 0;
//}

//了解了函数指针变量，那就要考虑如何使用函数指针变量
//通过函数指针调⽤指针指向的函数
//#include <stdio.h>
//int Add(int x, int y)
//{
//	return x + y;
//}
//
//int main()
//{
//	int (*pa)(int, int) = Add;
//
//	printf("%d\n", Add(3, 5));
//	printf("%d\n", pa(3,5));
//	printf("%d\n", (*pa)(3,5));	//注意：解引用的对象是指针变量，注意*的优先级，建议时刻带上小括号，以免发生不必要的错误。
//	return 0;
//}


//学会了函数指针变量，我们可以尝试理解以下代码
//这里我们需要加深一下：函数指针：是指向某函数的地址
//1、 (*(void (*)())0)();
//void (*)()这是一个函数指针类型，它的含义是指向无参数无返回值的类型/这个也就是函数指针变量的格式：数据类型（*指针变量）（参数类型/个数，参数类型/个数）
//后续变成：(void (*)()),它的含义是强制类型的转换
//再来理解：(void (*)())0：这是将整数 0 强制转换为函数指针类型，也就是把内存地址 0 当作一个函数指针，函数指针指向某函数的地址
//(*(void (*)())0)，这里就是解引用0这个函数指针，我们将其记成:a
//然后总体理解：a();也就是调用内存地址为0的函数。
//总而言之，我们首先要知道，函数指针类型，然后在一个名称之前加上一个类型，也就是将这个变量名称（不管这个变量名称之前是什么数据类型）强制转换成对应的数据类型，然后解引用之后就是对应变量名的函数。
//所以这个代码的突破口在：0

//2、void (*signal(int , void(*)(int)))(int);
// 
// 这里我们要清楚，指针变量不能是函数，所以这里的signal()主要是取得它的返回值进行后续判断的，又因为函数有个*，所以它的返回值就是函数指针
// 
//由上一个代码，我们很快就能抓住重点：signal()这个函数
//所以我们开始进行拆解：void (*signal())(int)，这是一个函数指针变量；void(*)(int)这种形式是一个函数指针类型；
//接着：signal(int,void(*)(int)):这就是一个signal函数，并且参数类型为int 和 void(*)(int)/函数指针类型，这个函数指针指向一个接受int类型参数返回的void（无返回值）的函数
//所以我们知道了signal(int,void(*)(int))只是返回一个值，并且这个值是一个函数指针
//然后再结合void(*函数指针)(int),就可以得出这是一个函数指针接受int类型的参数并且没有返回值的一个函数声明。

//这里我们得出一个方法：对于复杂声明，使用从右向左，由内向外的阅读规则


//typedef关键字
//这里可以根据上面的复杂的函数声明来进行优化

//首先我们需要定义这个函数指针类型void(*)(int),将定义的新名字放在*旁边
//我们将 void (*signal(int , void(*)(int)))(int) 进行简单化
// // typedef void(*pa)(int);
 // pa signal(int, pa);	//这里需要注意不要把pa当成函数使用->pa()是不对的


//函数指针数组
//定义，比如我有3个函数，那么我要存放在指针数组里面，就需要定义
//int (*parr1[3])() 这样就完成了定义，其指针类型为：int(*)(),与函数指针区别在于他包含数组的确切大小。


//用函数指针数组进行转移表的尝试
//计算器：包含加减乘除
#include <stdio.h>
int Add(int x,int y)
{
	return x + y;
}

int Sub(int x,int y)
{
	return x - y;
}

int Mul(int x,int y)
{
	return x * y;
}

int Div(int x,int y)
{
	return x / y;
}

void menu()
{
	
	printf("*******************\n");
	printf("***1、Add 2、Sub***\n");
	printf("***3、Mul 4、Div***\n");
	printf("******0、exit******\n");
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int r = 0;
	//函数指针数组的定义最主要的是指针变量多了数组大小。
	int (* pr[5])(int x, int y) = {0, Add,Sub,Mul,Div };	//这里是函数指针数组的定义，数组大小设置成5是为了方便输入的数对应其功能。
	do
	{
		menu();
		
		scanf("%d", &input);
		//未使用函数指针数组
		/*switch (input)
		{
		case 1:
			printf("请输入两个整数:");
			scanf("%d %d", &x, &y);
			 r = Add(x,y);
			printf("%d\n", r);
			break;
		case 2:
			printf("请输入两个整数:");
			scanf("%d %d", &x, &y);
			 r = Sub(x,y);
			printf("%d\n", r);
			break;
		case 3:
			printf("请输入两个整数:");
			scanf("%d %d", &x, &y);
			 r = Mul(x,y);
			printf("%d\n", r);
			break;
		case 4:
			printf("请输入两个整数:");
			scanf("%d %d", &x, &y);
			 r = Div(x,y);
			printf("%d\n", r);
			break;
		case 0:
			break;
		default:
			{
				printf("输入有误，请重新输入\n");
			}
		}*/

		//使用函数指针数组进行优化
		if (input <= 4 && input > 0)
		{
			printf("请输入两个整数:");
			scanf("%d %d", &x, &y);
			r = (*pr[input])(x, y);	//这里解引用获取数组下标对应函数。
			printf("%d\n", r);
		}
		else if (input == 0)
		{
			printf("你已退出计算器\n");
			break;
		}
		else
		{
			printf("输入有误，请重新输入\n");
		}

	} while (input);
	
	

	return 0;
}